﻿using AutoMapper;
using MRSite.AutoMapper;
using MRSite.Cache.Generic;
using MRSite.Common;
using MRSite.Entity;
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace MRSite.Cache.Entities
{
    public class EntityCache<TE, TItem, TP, TDB> :
        IEntityCache<TItem, TP>
        where TDB : DataContext
        where TE : class, IEntity<TP>
    {
        public EntityCache(
            ICacheManager cacheManager,
            TDB dataContext,
            string cacheName = null
            )
        {
            CacheManager = cacheManager;
            DataContext = dataContext;
            CacheName = cacheName ?? GeneraticCacheName();
            //CommonMapper = commonMapper;
        }

        protected virtual string GeneraticCacheName()
        {
            return GetType().FullName;
        }

        public TItem this[TP id]
        {
            get { return Get(id); }
        }

        public string CacheName { get; private set; }

        public IGenericCache<TP, TItem> InnerCache
        {
            get
            {
                return CacheManager.GetCache<TP, TItem>(CacheName);
            }

        }

        public TItem Get(TP id)
        {
            return InnerCache.Get(id, () => GetItemFromDatabase(id));
        }

        protected virtual TItem GetItemFromDatabase(TP id)
        {
            var entity = DataContext.GetTable<TE>().FirstOrDefault(CreateEqualityExpressionForId(id));
            return MapToCacheItem(entity);
        }

        protected virtual TItem MapToCacheItem(TE entity)
        {
            return CommonMapper.Map<TItem>(entity);
        }

        public ICommonMapper CommonMapper { get; set; }

        public Task<TItem> GetAsync(TP id)
        {
            return InnerCache.GetAsync(id, () => GetItemFromDatabaseAsync(id));
        }

        private Task<TItem> GetItemFromDatabaseAsync(TP id)
        {
            return Task.FromResult(MapToCacheItem(DataContext.GetTable<TE>().FirstOrDefault(CreateEqualityExpressionForId(id))));
        }

        protected ICacheManager CacheManager { get; private set; }

        protected TDB DataContext { get; set; }


        protected static Expression<Func<TE, bool>> CreateEqualityExpressionForId(TP id)
        {
            var lambdaParam = Expression.Parameter(typeof(TE));

            var lambdaBody = Expression.Equal(
                Expression.PropertyOrField(lambdaParam, "id"),
                Expression.Constant(id, typeof(TP))
                );

            return Expression.Lambda<Func<TE, bool>>(lambdaBody, lambdaParam);
        }
    }

    public class EntityCache<TE, TItem, TDB>
        : EntityCache<TE, TItem, int, TDB>,
        IEntityCache<TItem>
        where TDB : DataContext
        where TE : class, IEntity
    {
        public EntityCache(
            ICacheManager cacheManager,
            TDB dataContext,
            string cacheName = null) :
            base(
                cacheManager,
                dataContext,
                cacheName)
        {
        }
    }
}
